package api

import (
	"fmt"
	"gitee.com/wayjin/fabric-extension/cryptoutil"
	"gitee.com/wayjin/fabric-extension/cryptoutil/caclient"
	"github.com/hyperledger/fabric/common/tools/cryptogen/ca"
	"github.com/hyperledger/fabric/common/tools/cryptogen/msp"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"strings"
)

type NodeIdentity struct {
	Name        string
	Secret      string
	Type        caclient.IdentityType
	OUs         bool
	Affiliation string
	Attrs       string
}

func NodeType(typ string) caclient.IdentityType {
	return caclient.IdentityType(typ)
}

func (nodeid NodeIdentity) generateCryptoCertsFromCA(client *caclient.Client, auth cryptoutil.KeyStore) (*caclient.IdentityCryptoCert, error) {
	req := caclient.NewRegisterRequest(nodeid.Name, nodeid.Secret, nodeid.Affiliation, nodeid.Type)
	if nodeid.Attrs != "" {
		attrs := strings.Split(nodeid.Attrs, ",")
		for _, attr := range attrs {
			kv := strings.Split(attr, "=")
			if len(kv) != 2 {
				continue
			}
			req.SetAttribute(kv[0], kv[1])
		}
	}
	return client.GetIdentityCryptoCerts(req, auth, nodeid.OUs)
}

func (nodeid NodeIdentity) Generate(caurl, adminCryptoPath, output string) error {
	if nodeid.Affiliation == "" {
		return fmt.Errorf("affiliation is required")
	}
	if caurl != "" {
		adminCerts, err := cryptoutil.LoadKeyStoreFromPath(adminCryptoPath, []byte(""))
		if err != nil {
			return err
		}
		client, err := caclient.NewClient2(caurl) //getClient(caurl)
		if err != nil {
			return err
		}
		idcert, err := nodeid.generateCryptoCertsFromCA(client, adminCerts)
		if err != nil {
			return err
		}
		return idcert.SaveToLocal(filepath.Join(output, nodeid.Name))
	}
	// TODO
	fmt.Println("nothing todo")
	return nil
}

func getAffiliation(domain string) string {
	return domain
}

func generateFromConfig(client *caclient.Client, config Config, output string) error {
	generate := func(orgSpecs []OrgSpec, typ caclient.IdentityType) error {
		for _, orgSpec := range orgSpecs {
			adminID := NodeIdentity{
				Name:        "Admin@" + orgSpec.Domain,
				Secret:      "123456",
				Type:        caclient.ID_ADMIN,
				OUs:         orgSpec.EnableNodeOUs,
				Affiliation: getAffiliation(orgSpec.Domain),
			}
			adminIdCerts, err := adminID.generateCryptoCertsFromCA(client, nil)
			if err != nil {
				return err
			}
			orgDirName := "ordererOrganizations"
			nodeDirName := "orderers"
			if typ == caclient.ID_PEER {
				orgDirName = "peerOrganizations"
				nodeDirName = "peers"
			}

			orgBasePath := filepath.Join(output, orgDirName, orgSpec.Domain)
			adminIdCerts.SaveToLocal(filepath.Join(orgBasePath, "users", adminID.Name))
			for _, node := range orgSpec.Specs {
				nodeID := NodeIdentity{
					Name: node.Hostname + "." + orgSpec.Domain,
					Secret: "123456",
					Type: typ,
					OUs: orgSpec.EnableNodeOUs,
					Affiliation: adminID.Affiliation,
				}
				nodeIdCerts, err := nodeID.generateCryptoCertsFromCA(client, adminIdCerts.GetMSPCert())
				if err != nil {
					return err
				}
				nodeIdCerts.SaveToLocal(filepath.Join(orgBasePath, nodeDirName, nodeID.Name))
			}
		}
		return nil
	}
	// beautiful code rule
	if err := generate(config.OrdererOrgs, caclient.ID_ORDERER); err == nil {
		return generate(config.PeerOrgs, caclient.ID_PEER)
	} else {
		return err
	}

}

func GenerateFromConfig(configFile, caurl, output string) error {
	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return err
	}
	var config Config
	err = yaml.Unmarshal(data, &config)
	if err != nil {
		return err
	}
	if caurl != "" {
		client, err := caclient.NewClient2(caurl) //getClient(caurl)
		if err != nil {
			return err
		}
		return generateFromConfig(client, config, output)
	}
	// TODO : generate local
	generateLocal(config, output)
	return nil
}

func generateLocal(config Config, output string) {
	for _, orgSpec := range config.PeerOrgs {
		err := renderOrgSpec(&orgSpec, "peer")
		if err != nil {
			fmt.Printf("Error processing peer configuration: %s", err)
			os.Exit(-1)
		}
		generatePeerOrg(output, orgSpec)
	}
	for _, orgSpec := range config.OrdererOrgs {
		err := renderOrgSpec(&orgSpec, "orderer")
		if err != nil {
			fmt.Printf("Error processing orderer configuration: %s", err)
			os.Exit(-1)
		}
		generateOrdererOrg(output, orgSpec)
	}
}
func getClient(urlraw string) (*caclient.Client, error) {
	var err error
	var purl *url.URL

	purl, err = url.Parse(urlraw)
	if err != nil {
		return nil, fmt.Errorf("invalid url: %v", err)
	}
	httpUrl := fmt.Sprintf("%s://%s", purl.Scheme, purl.Host)
	username := purl.User.Username()
	passwd, _ := purl.User.Password()
	return caclient.NewClient(httpUrl, username, passwd)
}

func generateNodes(baseDir string, nodes []NodeSpec, signCA *ca.CA, tlsCA *ca.CA, nodeType int, nodeOUs bool) {
	for _, node := range nodes {
		nodeDir := filepath.Join(baseDir, node.CommonName)
		if _, err := os.Stat(nodeDir); os.IsNotExist(err) {
			currentNodeType := nodeType
			if node.isAdmin && nodeOUs {
				currentNodeType = msp.ADMIN
			}
			err := msp.GenerateLocalMSP(nodeDir, node.CommonName, node.SANS, signCA, tlsCA, currentNodeType, nodeOUs)
			if err != nil {
				fmt.Printf("Error generating local MSP for %v:\n%v\n", node, err)
				os.Exit(1)
			}
		}
	}
}

func generatePeerOrg(baseDir string, orgSpec OrgSpec) {
	orgName := orgSpec.Domain

	fmt.Println(orgName)
	// generate CAs
	orgDir := filepath.Join(baseDir, "peerOrganizations", orgName)
	caDir := filepath.Join(orgDir, "ca")
	tlsCADir := filepath.Join(orgDir, "tlsca")
	mspDir := filepath.Join(orgDir, "msp")
	peersDir := filepath.Join(orgDir, "peers")
	usersDir := filepath.Join(orgDir, "users")
	adminCertsDir := filepath.Join(mspDir, "admincerts")
	// generate signing CA
	signCA, err := ca.NewCA(caDir, orgName, orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
	if err != nil {
		fmt.Printf("Error generating signCA for org %s:\n%v\n", orgName, err)
		os.Exit(1)
	}
	// generate TLS CA
	tlsCA, err := ca.NewCA(tlsCADir, orgName, "tls"+orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
	if err != nil {
		fmt.Printf("Error generating tlsCA for org %s:\n%v\n", orgName, err)
		os.Exit(1)
	}

	err = msp.GenerateVerifyingMSP(mspDir, signCA, tlsCA, orgSpec.EnableNodeOUs)
	if err != nil {
		fmt.Printf("Error generating MSP for org %s:\n%v\n", orgName, err)
		os.Exit(1)
	}

	generateNodes(peersDir, orgSpec.Specs, signCA, tlsCA, msp.PEER, orgSpec.EnableNodeOUs)

	// TODO: add ability to specify usernames
	users := []NodeSpec{}
	for j := 1; j <= orgSpec.Users.Count; j++ {
		user := NodeSpec{
			CommonName: fmt.Sprintf("%s%d@%s", userBaseName, j, orgName),
		}

		users = append(users, user)
	}
	// add an admin user
	adminUser := NodeSpec{
		isAdmin:    true,
		CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
	}

	users = append(users, adminUser)
	generateNodes(usersDir, users, signCA, tlsCA, msp.CLIENT, orgSpec.EnableNodeOUs)

	// copy the admin cert to the org's MSP admincerts
	if !orgSpec.EnableNodeOUs {
		err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
		if err != nil {
			fmt.Printf("Error copying admin cert for org %s:\n%v\n",
				orgName, err)
			os.Exit(1)
		}
	}

	// copy the admin cert to each of the org's peer's MSP admincerts
	for _, spec := range orgSpec.Specs {
		if orgSpec.EnableNodeOUs {
			continue
		}
		err = copyAdminCert(usersDir,
			filepath.Join(peersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
		if err != nil {
			fmt.Printf("Error copying admin cert for org %s peer %s:\n%v\n",
				orgName, spec.CommonName, err)
			os.Exit(1)
		}
	}
}

func generateOrdererOrg(baseDir string, orgSpec OrgSpec) {

	orgName := orgSpec.Domain

	// generate CAs
	orgDir := filepath.Join(baseDir, "ordererOrganizations", orgName)
	caDir := filepath.Join(orgDir, "ca")
	tlsCADir := filepath.Join(orgDir, "tlsca")
	mspDir := filepath.Join(orgDir, "msp")
	orderersDir := filepath.Join(orgDir, "orderers")
	usersDir := filepath.Join(orgDir, "users")
	adminCertsDir := filepath.Join(mspDir, "admincerts")
	// generate signing CA
	signCA, err := ca.NewCA(caDir, orgName, orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
	if err != nil {
		fmt.Printf("Error generating signCA for org %s:\n%v\n", orgName, err)
		os.Exit(1)
	}
	// generate TLS CA
	tlsCA, err := ca.NewCA(tlsCADir, orgName, "tls"+orgSpec.CA.CommonName, orgSpec.CA.Country, orgSpec.CA.Province, orgSpec.CA.Locality, orgSpec.CA.OrganizationalUnit, orgSpec.CA.StreetAddress, orgSpec.CA.PostalCode)
	if err != nil {
		fmt.Printf("Error generating tlsCA for org %s:\n%v\n", orgName, err)
		os.Exit(1)
	}

	err = msp.GenerateVerifyingMSP(mspDir, signCA, tlsCA, orgSpec.EnableNodeOUs)
	if err != nil {
		fmt.Printf("Error generating MSP for org %s:\n%v\n", orgName, err)
		os.Exit(1)
	}

	generateNodes(orderersDir, orgSpec.Specs, signCA, tlsCA, msp.ORDERER, orgSpec.EnableNodeOUs)

	adminUser := NodeSpec{
		isAdmin:    true,
		CommonName: fmt.Sprintf("%s@%s", adminBaseName, orgName),
	}

	// generate an admin for the orderer org
	users := []NodeSpec{}
	// add an admin user
	users = append(users, adminUser)
	generateNodes(usersDir, users, signCA, tlsCA, msp.CLIENT, orgSpec.EnableNodeOUs)

	// copy the admin cert to the org's MSP admincerts
	if !orgSpec.EnableNodeOUs {
		err = copyAdminCert(usersDir, adminCertsDir, adminUser.CommonName)
		if err != nil {
			fmt.Printf("Error copying admin cert for org %s:\n%v\n",
				orgName, err)
			os.Exit(1)
		}
	}

	// copy the admin cert to each of the org's orderers's MSP admincerts
	for _, spec := range orgSpec.Specs {
		if orgSpec.EnableNodeOUs {
			continue
		}
		err = copyAdminCert(usersDir,
			filepath.Join(orderersDir, spec.CommonName, "msp", "admincerts"), adminUser.CommonName)
		if err != nil {
			fmt.Printf("Error copying admin cert for org %s orderer %s:\n%v\n",
				orgName, spec.CommonName, err)
			os.Exit(1)
		}
	}

}
